#include <atomic>
#include <chrono>
#include <cstdint>
#include <map>
#include <thread>

#include <glog/logging.h>

int64_t GetCurrentTimestamp() {
  const auto curr_time = std::chrono::high_resolution_clock::now();
  return curr_time.time_since_epoch().count();
}

static bool HasLoggedMessageSinceWeakThreadSafe(
    const char* file_name,
    int line_number,
    int64_t past_nanoseconds,
    int64_t timestamp) {
  static std::map<std::string, int64_t> logged_messages;
  static std::atomic<bool> in_use{false};
  bool unused = false;
  bool inused = true;

  int64_t when = timestamp - past_nanoseconds;
  std::stringstream str;
  str << file_name << ":" << line_number;
  const std::string key = str.str();
  if (!in_use.compare_exchange_strong(unused, inused)) {
    return true;  // do not log
  }
  const auto& insert_pair = logged_messages.insert(std::make_pair(key, timestamp));
  bool ret = false;
  if (!insert_pair.second) {
    if (insert_pair.first->second >= when) {
      ret = true;
    } else {
      insert_pair.first->second = timestamp;
      ret = false;
    }
  }
  in_use.compare_exchange_strong(inused, unused);
  (void)HasLoggedMessageSinceWeakThreadSafe;

  return ret;
}

static bool HasLoggedMessageSinceStrongThreadSafe(
    const char* file_name,
    int line_number,
    int64_t past_nanoseconds,
    int64_t timestamp) {
  static std::map<std::string, int64_t> logged_messages;
  static std::atomic<bool> in_use{false};

  int64_t when = timestamp - past_nanoseconds;
  std::stringstream str;
  str << file_name << ":" << line_number;
  const std::string key = str.str();
  bool ret = false;
  while (true) {
    bool unused = false;
    bool inused = true;
    if (!in_use.compare_exchange_strong(unused, inused)) {
      // std::this_thread::sleep_for(std::chrono::nanoseconds(10));
      continue;
    }
    const auto& insert_pair = logged_messages.insert(std::make_pair(key, timestamp));
    if (!insert_pair.second) {
      if (insert_pair.first->second >= when) {
        ret = true;
      } else {
        insert_pair.first->second = timestamp;
        ret = false;
      }
    }
    in_use.compare_exchange_strong(inused, unused);
    break;
  }
  (void)HasLoggedMessageSinceStrongThreadSafe;

  return ret;
}

#define LOG_EVERY_NS(log_level, past_nanoseconds, timestamp)                           \
  HasLoggedMessageSinceWeakThreadSafe(__FILE__, __LINE__, past_nanoseconds, timestamp) \
      ? void()                                                                         \
      : google::LogMessageVoidify() & LOG(log_level)

#define LOG_EVERY_NS_2(log_level, past_nanoseconds, timestamp)                           \
  HasLoggedMessageSinceStrongThreadSafe(__FILE__, __LINE__, past_nanoseconds, timestamp) \
      ? void()                                                                           \
      : google::LogMessageVoidify() & LOG(log_level)

void Print() {
  while (true) {
    LOG_EVERY_NS(INFO, 10'000'000'000LL, GetCurrentTimestamp()) << "test ...";
  }
}

int main() {
  std::thread t1(Print);
  std::thread t2(Print);
  std::thread t3(Print);
  t1.join();
  t2.join();
  t3.join();
  return 0;
}
